/*
 Copyright 2021 Peter Harrison

 Licensed under the Apache License, Version 2.0 (the "License");
 you may not use this file except in compliance with the License.
 You may obtain a copy of the License at

      https://www.apache.org/licenses/LICENSE-2.0

 Unless required by applicable law or agreed to in writing, software
 distributed under the License is distributed on an "AS IS" BASIS,
 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 See the License for the specific language governing permissions and
 limitations under the License.

------------------------------------------------------------------------------

 BasicEncoder provides a class for reading a rotary encoder knob.
 It is not suited to motor encoders.
 The switch, if present, needs a deparate button library .

 Decoding logic based on https://www.mikrocontroller.net/articles/Drehgeber
 */

#ifndef BASIC_ENCODER_H_
#define BASIC_ENCODER_H_

#include <Arduino.h>
// 解决编译错误，为什么这样修改？
uint8_t SREG;

class BasicEncoder {
 public:
  BasicEncoder(int8_t pinA, int8_t pinB, uint8_t active_state = LOW, uint8_t steps = 4)
      : m_pin_a(pinA), m_pin_b(pinB), m_pin_active(active_state), m_steps_per_count(steps) {
    pinMode(pinA, INPUT_PULLUP);
    pinMode(pinB, INPUT_PULLUP);
    m_previous_state = pin_state();
    m_change = 0;
  }
  ~BasicEncoder() {}

  void begin() { reset(); }

  int8_t pin_state() {
    int8_t state_now = 0;
    if (digitalRead(m_pin_a) == m_pin_active) {
      state_now |= 2;
    }
    if (digitalRead(m_pin_b) == m_pin_active) {
      state_now |= 1;
    }
    return state_now;
  }

  // to update the encoder changes
  // call this method in a timmer interrupt for best performance
  // it could also be called in the main loop
  // this takes about 10-15us using digitalRead on Arduino Nano
  void service() {
    int8_t state_now = pin_state();
    state_now ^= state_now >> 1;  // two bit gray-to-binary
    int8_t difference = m_previous_state - state_now;
    // bit 1 has the direction, bit 0 is set if changeed
    if (difference & 1) {
      m_previous_state = state_now;
      int delta = (difference & 2) - 1;
      if (m_reversed) {
        delta = -delta;
      }
      m_change += delta;
      m_steps += delta;
    }
  }

  /****************************************************************

  // try this if there are unwanted transitions at the detents
  // it is half resolution so counts are doubled
  const int8_t decode_table[16] PROGMEM = {0, 0, -2, 0, 0, 0, 0, 2, 2, 0, 0, 0, 0, -2, 0, 0};
  void flaky_encoder_service(void) {
    // just get the bits - no gray-to-binary conversion needed
    int8_t state_now = pin_state();
    static int8_t encoder_state = state_now;
    encoder_state = ((encoder_state << 2) | state_now) & 0x0f;
    encoder_change += (int8_t)pgm_read_byte(&decode_table[encoder_state]);
  }

  ****************************************************************/

  // Read changes frequently enough that overflows cannot happen.
  int8_t get_change() {
    uint8_t sreg = SREG;  // save the current interrupt enable flag
    noInterrupts();
    int8_t change = m_change;
    // the switch statement can make better code because only optimised
    // operations are used instead of generic division
    switch (m_steps_per_count) {
      case 4:
        m_change %= 4;
        change /= 4;
        break;
      case 2:
        m_change %= 2;
        change /= 2;
        break;
      default:
        m_change = 0;
        break;
    }
    SREG = sreg;  // restore the previous interrupt enable flag state
    return change;
  }

  int get_count() {
    uint8_t sreg = SREG;  // save the current interrupt enable flag
    noInterrupts();
    int count = m_steps / m_steps_per_count;
    SREG = sreg;  // restore the previous interrupt enable flag state
    return count;
  }

  void reset() {
    uint8_t sreg = SREG;  // save the current interrupt enable flag
    noInterrupts();
    m_steps = 0;
    m_change = 0;
    SREG = sreg;  // restore the previous interrupt enable flag state
  }

  void set_reverse() { m_reversed = true; }

  void set_forward() { m_reversed = false; }

 private:
  int8_t m_pin_a = 0;
  int8_t m_pin_b = 0;
  uint8_t m_pin_active = LOW;
  uint8_t m_steps_per_count = 4;
  bool m_reversed = false;
  volatile int m_change = 0;
  int8_t m_previous_state = 0;
  int m_steps = 0;
};
#endif  // BASIC_ENCODER_H_
